use darling::{FromDeriveInput, FromVariant};
use proc_macro2::TokenStream;
use quote::quote;
use syn::DeriveInput;

#[allow(unused)]
#[derive(Debug, FromDeriveInput)]
#[darling(attributes(error_info))]
struct ErrorDerviveData {
    ident: syn::Ident,
    generics: syn::Generics,
    data: darling::ast::Data<EnumVariants, ()>,
    app_type: syn::Type,
    prefix: String,
}

#[allow(unused)]
#[derive(Debug, FromVariant)]
#[darling(attributes(error_info))]
struct EnumVariants {
    ident: syn::Ident,
    fields: darling::ast::Fields<darling::util::Ignored>,
    code: String,
    #[darling(default)]
    app_code: String,
    #[darling(default)]
    cli_msg: String,
}

pub(crate) fn process_error_info(input: DeriveInput) -> TokenStream {
    let ErrorDerviveData {
        ident: name,
        generics,
        data: darling::ast::Data::Enum(variants),
        app_type,
        prefix,
    } = ErrorDerviveData::from_derive_input(&input).expect("parse derive input data failed")
    else {
        panic!("Only enum is supported")
    };

    let code = variants
        .iter()
        .map(|v| {
            let EnumVariants {
                ident,
                code,
                app_code,
                cli_msg,
                fields,
            } = v;
            let code = format!("{}{}", prefix, code);

            let variant_code = match fields.style {
                darling::ast::Style::Struct => quote! {
                    #name::#ident{ .. }
                },
                darling::ast::Style::Tuple => quote! {
                    #name::#ident(_)
                },
                darling::ast::Style::Unit => quote! {
                    #name::#ident
                },
            };
            quote! {
                #variant_code => {
                    ErrorInfo::new(
                        #app_code,
                        #code,
                        #cli_msg,
                        self,
                    )
                }
            }
        })
        .collect::<Vec<_>>();

    quote! {
        use error_code::{ErrorInfo, ToErrorInfo};
        impl #generics ToErrorInfo for #name #generics {
            type T = #app_type;
            fn to_error_info(&self) -> ErrorInfo<Self::T> {
                match self {
                    #(#code),*
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn darling_data_struct_test() -> anyhow::Result<()> {
        let input = r#"
            #[derive(thiserror::Error, ToErrorInfo)]
            #[error_info(app_type = "http::StatusCode", prefix = "01")]
            pub enum XError {
                #[error("Invalid command: {0}")]
                #[error_info(code = "IC", app_code = "400")]
                InvalidCommand(String),

                #[error("Invalid argument: {0}")]
                #[error_info(code = "IA", app_code = "400", cli_msg = "friendly message")]
                InvalidArgument(String),

                #[error("{0}")]
                #[error_info(code = "RE", app_code = "500")]
                RespError(#[from] std::io::Error),
            }
        "#;
        let parsed = syn::parse_str(input).unwrap();
        let _info = ErrorDerviveData::from_derive_input(&parsed).unwrap();

        // println!("{:#?}", info);

        let _code = process_error_info(parsed);
        // println!("{:#?}", code);

        Ok(())
    }
}
